//---------------------------------------------------------------------------------
// Copyright (c) 2008 VoiceLynx Project
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
//     1. The origin of this software must not be misrepresented; you must not
//        claim that you wrote the original software. If you use this software
//        in a product, an acknowledgment in the product documentation would be
//        appreciated but is not required.
//
//     2. Altered source versions must be plainly marked as such, and must not be
//        misrepresented as being the original software.
//
//     3. This notice may not be removed or altered from any source
//        distribution.
//---------------------------------------------------------------------------------

#include "VlCircularBuffer.h"


VLCircularBuffer::VLCircularBuffer(const size_t bufferSize) 
: size(bufferSize + 1),
writePtr(0),
readPtr(0)
{
  buffer = new VLByte[size];
}

VLCircularBuffer::~VLCircularBuffer()
{
  delete[] buffer;
}

bool VLCircularBuffer::append(const VLByte *data, size_t len)
{
  bool returnVal = false;
  if(pending() + len < size)
  {
    // Get the first copy length - it will be the length of data we need to copy,
    // up to the end of the buffer
    int copy1len = std::min<size_t>(size - writePtr, len);
    memcpy(buffer + writePtr, data, copy1len);
    writePtr += copy1len;
    data += copy1len;
    len -= copy1len;

    // wrap the write pointer if necessary
    if (writePtr == size)
    {
      writePtr = 0;

      // if we wrapped, we may have more to copy
      int copy2len = len;
      memcpy(buffer + writePtr, data, copy2len);

      writePtr += copy2len;
    }

    returnVal = true;
  }

  assert(readPtr < size);
  assert(writePtr < size);

  return returnVal;
}

bool VLCircularBuffer::consume(VLByte *data, size_t len)
{
  bool returnVal = false;

  if (pending() + len < size)
  {
    int copy1len = std::min<size_t>(size - readPtr, len);

    memcpy(data, buffer + readPtr, copy1len);
    readPtr += copy1len;
    data += copy1len;
    len -= copy1len;

    if (readPtr == size)
    {
      readPtr = 0;

      // if we wrapped, we may have more to copy
      int copy2len = len;
      memcpy(data, buffer + readPtr, copy2len);
      readPtr += copy2len;
    }

    returnVal = true;
  }

  assert(readPtr < size);
  assert(writePtr < size);

  return returnVal;
}

size_t VLCircularBuffer::pending()
{
  size_t returnVal;

  if (readPtr <= writePtr)
  {
    returnVal = writePtr - readPtr;
  }
  else
  {
    returnVal = (size - readPtr) + writePtr;
  }

  return returnVal;
}

bool VLCircularBuffer::give(VLByteArrayPtr data)
{
  bool returnVal = false;

  if (data.get() != 0 && data->size() + pending() < size)
  {
    append(&(*data.get())[0], data->size());

    returnVal = true;
  }

  return returnVal;
}

VLByteArrayPtr VLCircularBuffer::take(VLU32 size)
{
  VLByteArrayPtr returnVal;

  if (size == 0)
  {
    returnVal.reset(new VLByteArray(0));
  }
  else if (pending() >= size)
  {
    returnVal.reset(new VLByteArray(size));

    consume(&(*returnVal.get())[0], size);
  }

  return returnVal;
}

VLByteArrayPtr VLCircularBuffer::takeMultipleOf(VLU32 sizeMultiple)
{
  VLByteArrayPtr returnVal;

  // Protect against div/0 and negative numbers
  if (sizeMultiple >= 0)
  {
    int size = pending() / sizeMultiple;

    returnVal = take(size * sizeMultiple);
  }

  return returnVal;
}

VLByteArrayPtr VLCircularBuffer::takeAll()
{
  VLByteArrayPtr returnVal;

  if (readPtr != writePtr)
  {
    returnVal = take(pending());
  }
  else
  {
    returnVal.reset(new VLByteArray(0));
  }
  
  return returnVal;
}
